package model.entity;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import model.ability.InteractiveAbility;
import model.ability.MapAbility;
import model.ability.PassiveSkill;
import model.area_effect.Trap;
import model.core.Renderable;
import model.item.Armor;
import model.item.BrawlWeapon;
import model.item.Inventory;
import model.item.InventoryItem;
import model.item.MapGold;
import model.item.OneHandedWeapon;
import model.item.RangedWeapon;
import model.item.StaffWeapon;
import model.item.TwoHandedWeapon;
import model.item.Weapon;
import model.map.MapPoint;
import model.mediators.CollisionMediator;
import model.mediators.DeathMediator;
import model.mediators.InteractionOption;
import model.mediators.ModelMovementMediator;
import model.stats.StatsContainer;
import model.stats.StatsPersistMods;
import model.stats.StatsTempMods;
import utility.DelayTimer;
import model.core.AbilitiesLibrary;
/**
 * 
 * @author srhinson
 *dropped redundant methods provided by interface(a method addToInventory(item) was the same as pickupItem(item)
 *changed vision to be a stat, and modified the get and set methods (wasn't well specified in doc
 *removed MovementSwitch, which had no use (supposedly was part of the old, half developed move delay system
 *changed offensiveAbilities to defaultAttack and added useDefaultAttack().  design removes multiple offensive abilities, puts spells in utility abilities.
 *		cause by the need to add or remove weapon skills with equiping weapons.
 *moved affectFavorability down to NPC to avoid mixed Instance Anti-cohesion
 *added makeHostile and notifyOfDeath to allow entities to maintain a list of hostile entities.
 *Added the getSkillList method to help with leveling skills.  wasn't addressed in design.
 *Altered gainExp method to add in check for gaining a level.  Wasn't covered in previous design
 *made stats protected so it could be leveled
 */
public abstract class Entity implements Renderable {
	
	public MapPoint getRespawnPoint() {
		return respawnPoint;
	}
	private String name;
	private String occupation;
	private MapPoint location;
	private MapPoint direction;
	protected StatsContainer stats;
	protected MapAbility defaultAttack;
	private MapAbilityList utilityAbilities;
	protected Inventory inventory;
	private ArrayList<InteractionOption> withInteractiveList;
	private ArrayList<InteractionOption> byInteractiveList;
	private Entity motorLegs;
//What is this supposed to do?	private MovementSwitch moveSwitch;
	private MovementType movementType;
	private PassiveSkill observe;
	private PassiveSkill bargain;
	private PassiveSkill bindwounds;
	private MapPoint respawnPoint;
	private ArrayList<InteractiveAbility> interactiveAbilityList;
	private ArrayList<Entity> deathSubscribers;
	private HashSet<Entity> hostilityList;
	private DirectionalGraphicSet directionalGraphicSet;
	private DelayTimer attackTimer;
	private DelayTimer moveTimer;
	
	public Entity() {
		name="dave";
		occupation="professer";
		location=new MapPoint(0,0);
		direction=new MapPoint(0,-1);
		stats=new StatsContainer();
		defaultAttack= AbilitiesLibrary.getInstance().getMapAbility("fisting");
		utilityAbilities=new MapAbilityList(this);
		inventory=new Inventory();
		withInteractiveList= new ArrayList<InteractionOption>();
		byInteractiveList= new ArrayList<InteractionOption>();
		motorLegs=this;
		movementType= new MovementType(1, 1, 1, 1, false, true, true, false);
		observe= new PassiveSkill("observe");
		bargain= new PassiveSkill("bargain");
		bindwounds= new PassiveSkill("bindWounds");
		respawnPoint=new MapPoint(0,0);
		interactiveAbilityList=new ArrayList<InteractiveAbility>();
		deathSubscribers=new ArrayList<Entity>();
		hostilityList=new HashSet<Entity>();
		directionalGraphicSet= new DirectionalGraphicSet();
		attackTimer=new DelayTimer();
		moveTimer=new DelayTimer();
	}
	/**
	 * constructor that specifies entities traits.
	 * @param name
	 * @param occName
	 * @param origin
	 * @param stats
	 * @param movement
	 * @param obs
	 * @param bar
	 * @param bind
	 */
	public Entity(String name, String occName, MapPoint origin, StatsContainer stats,
			MovementType movement, int obs, int bar, int bind, DirectionalGraphicSet graphics) {
		this.name=name;
		occupation=occName;
		location=origin;
		direction=new MapPoint(0,-1);
		this.stats=stats;
		defaultAttack=AbilitiesLibrary.getInstance().getMapAbility("fisting");
		utilityAbilities=new MapAbilityList(this);
		inventory=new Inventory();
		withInteractiveList= new ArrayList<InteractionOption>();
		byInteractiveList= new ArrayList<InteractionOption>();
		motorLegs=this;
		movementType= movement;
		observe= new PassiveSkill("observe",obs);
		bargain= new PassiveSkill("bargain",bar);
		bindwounds= new PassiveSkill("bindWounds",bind);
		respawnPoint = origin;
		interactiveAbilityList=new ArrayList<InteractiveAbility>();
		deathSubscribers=new ArrayList<Entity>();
		hostilityList=new HashSet<Entity>();
		directionalGraphicSet= graphics;
		attackTimer=new DelayTimer();
		moveTimer=new DelayTimer();
	}
	
	public int getObservationSkill(){
		return observe.getLevel();
	}
	public String getName(){
		return name;
	}
	public Iterator<InteractiveAbility> interactiveAbilityListIterator() {
		return interactiveAbilityList.iterator();
	}
	public void initInteractiveAbilities() {

	}
	public void setDirectionNorth() {
		direction = MapPoint.NORTH;
	}
	
	public void setDirectionNE() {
		direction = MapPoint.NORTH;
	}
	
	public void setDirectionNW() {
		direction = MapPoint.NORTH;
	}
	
	public void setDirectionSouth() {
		direction = MapPoint.NORTH;
	}
	
	public void setDirectionSE() {
		direction = MapPoint.NORTH;
	}
	
	public void setDirectionSW() {
		direction = MapPoint.NORTH;
	}
	
	public void commandMoveNorth() {
		motorLegs.attemptToMoveNorth();
	}
	
	public void commandMoveNE() {
		motorLegs.attemptToMoveNE();
	}
	
	public void commandMoveNW() {
		motorLegs.attemptToMoveNW();
	}
	
	public void commandMoveSouth() {
		motorLegs.attemptToMoveSouth();
	}
	
	public void commandMoveSE() {
		motorLegs.attemptToMoveSE();
	}
	
	public void commandMoveSW() {
		motorLegs.attemptToMoveSW();
	}
	
	
	public void attemptToMoveNorth() {
		setDirectionNorth();
		if(moveTimer.isDelayed())return;
		ModelMovementMediator.checkedMoveNorth(this);
		
	}

	public void attemptToMoveNE() {
		setDirectionNE();
		if(moveTimer.isDelayed())return;
		ModelMovementMediator.checkedMoveNE(this);
		
	}
	
	public void attemptToMoveNW() {
		setDirectionNW();
		if(moveTimer.isDelayed())return;
		ModelMovementMediator.checkedMoveNW(this);
		
	}
	
	public void attemptToMoveSouth() {
		setDirectionSouth();
		if(moveTimer.isDelayed())return;
		ModelMovementMediator.checkedMoveSouth(this);
		
	}
	
	public void attemptToMoveSE() {
		setDirectionSE();
		if(moveTimer.isDelayed())return;
		ModelMovementMediator.checkedMoveSE(this);
	}
	
	public void attemptToMoveSW() {
		setDirectionSW();
		if(moveTimer.isDelayed())return;
		ModelMovementMediator.checkedMoveSW(this);
		
	}
	
	public void mount(Mount mount) {
		motorLegs = mount;
	}
	
	public void dismount() {
		motorLegs = this;
	}
	
	public void pickUpGold(MapGold gold) {
		inventory.addGold(gold.getAmount());
	}
	
	public void pickUpItem(InventoryItem item) {
		inventory.addItem(item);
	}
	
	public void equipOneHand(OneHandedWeapon weapon) {
		inventory.equipWeapon(this, weapon);
	}
	
	public void equipTwoHand(TwoHandedWeapon weapon) {
		inventory.equipWeapon(this, weapon);
	}
	
	public void equipBrawl(BrawlWeapon weapon) {
		inventory.equipWeapon(this, weapon);
	}
	
	public void equipStaff(StaffWeapon weapon) {
		inventory.equipWeapon(this, weapon);
	}
	
	public void equipRanged(RangedWeapon weapon) {
		inventory.equipWeapon(this, weapon);
	}
	
	public void equipArmor(Armor armor){
		inventory.equipArmor(this, armor);
	}
	
	public void equipWeapon(Weapon weapon){
		inventory.equipWeapon(this, weapon);
	}
	
	public void unequipArmor() {
		inventory.unequipArmor(this);
	}
	
	public void unequipWeapon() {
		inventory.unequipArmor(this);
		defaultAttack=AbilitiesLibrary.getInstance().getMapAbility("fisting");
	}
	
	public void heal(int amount) {
		stats.gainHealth(amount);
	}
	
	public void harm(int amount) {
		stats.loseHealth(amount);
	}
	
	public void gainMana(int amount) {
		stats.gainMana(amount);
	}
	
	public void useMana(int amount) {
		stats.loseMana(amount);
	}
	
	public void applyStatEffect(StatsTempMods statMods) {
		stats.addStatsTempMods(statMods);
	}
	
	public void disable(int delay){
		attackTimer.delay(delay);
		moveTimer.delay(delay);
	}
	public void diasbleAttack(int delay){
		attackTimer.delay(delay);
	}
	public void disableMovement(int delay){
		attackTimer.delay(delay);
	}
	public int getViewRange() {
		return stats.getVisionRange();
	}
	
	public void gainExp(int experience){
		int old = stats.getLevel();
		stats.gainExp(experience);
		if(stats.getLevel()>old)this.levelup();
	}
	public abstract void levelup();
	public void deathHandler(){
		if (stats.getLives() > 0) {
			DeathMediator.respawn(this);
		}
		
		else {
			DeathMediator.death(this);
			Iterator<Entity> iterator = deathSubscribers.iterator();
			while (iterator.hasNext()) {
				iterator.next().notifyOfDeath(this);
			}
		}
	}

	public abstract void notifyOfDeath(Entity entity);

	public void registerToReceiveDeathNotification(Entity entity) {
		deathSubscribers.add(entity);
	}
	public StatsContainer getStatsContainer() {
		return stats;
	}


	public void putToSleep(int sleepDuration) {
		disable(sleepDuration);
	}

	
	public MovementType getMovementType() {
		return movementType;
	}

	public MapPoint getDirection() {
		return direction;
	}

	public ArrayList<InteractionOption> getWithInteractiveList() {
		return withInteractiveList;
	}

	public ArrayList<InteractionOption> getByInteractiveList() {
		return byInteractiveList;
	}

	public Inventory getInventory() {
		return inventory;
	}

	public MapPoint getLocation() {
		return location;
	}

	public void setLocation(MapPoint location) {
		this.location = location;
		
	}

	public abstract void interactWith(List<Entity> entityList);
	
	
	public void applyTrap(Trap trap){
		trap.getEffect().doEffect(this);
	}

	public abstract void enchant(int baseValue);
	
	public void addStatsPersistMods(StatsPersistMods mods){
		stats.addStatsPersistMods(mods);
	}
	public void removeStatsPersistMods(StatsPersistMods mods){
		stats.removeStatsPersistMods(mods);
	}
	public Set<String> getDirectionalGraphicsSet(){
		return directionalGraphicSet.getDirectionalGraphicsSet();
	}
	
	public String getGraphicID() {
		return directionalGraphicSet.get(direction);
	}
	
	public void useDefaultAttack(){
		defaultAttack.useMapAbility(this);
	}
	
	public void update() {
		CollisionMediator.collide(this);
		attackTimer.update();
		moveTimer.update();
	}
	
	public abstract void makeHostile(Entity caster);
    public abstract void affectFavorability(int mag, int dur);
    
    public HashSet<PassiveSkill> getSkillList(){
    	HashSet<PassiveSkill> skillList = new HashSet<PassiveSkill>();
    	skillList.add(bargain);
    	skillList.add(observe);
    	skillList.add(bindwounds);
    	return skillList;
    }
}